#include "StdAfx.h"

#ifdef _DEBUG
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")
#endif

namespace DuiLib {

    /////////////////////////////////////////////////////////////////////////////////////
    //
    //

    void DUILIB_API DUI__Trace(LPCTSTR pstrFormat, ...)
    {
#ifdef _DEBUG
        TCHAR szBuffer[300] = { 0 };
        va_list args;
        va_start(args, pstrFormat);
        ::wvnsprintf(szBuffer, lengthof(szBuffer) - 2, pstrFormat, args);
        _tcscat(szBuffer, _T("\n"));
        va_end(args);
        ::OutputDebugString(szBuffer);
#endif
    }

    LPCTSTR DUI__TraceMsg(UINT uMsg)
    {
#define MSGDEF(x) if(uMsg==x) return _T(#x)
        MSGDEF(WM_SETCURSOR);
        MSGDEF(WM_NCHITTEST);
        MSGDEF(WM_NCPAINT);
        MSGDEF(WM_PAINT);
        MSGDEF(WM_ERASEBKGND);
        MSGDEF(WM_NCMOUSEMOVE);
        MSGDEF(WM_MOUSEMOVE);
        MSGDEF(WM_MOUSELEAVE);
        MSGDEF(WM_MOUSEHOVER);
        MSGDEF(WM_NOTIFY);
        MSGDEF(WM_COMMAND);
        MSGDEF(WM_MEASUREITEM);
        MSGDEF(WM_DRAWITEM);
        MSGDEF(WM_LBUTTONDOWN);
        MSGDEF(WM_LBUTTONUP);
        MSGDEF(WM_LBUTTONDBLCLK);
        MSGDEF(WM_RBUTTONDOWN);
        MSGDEF(WM_RBUTTONUP);
        MSGDEF(WM_RBUTTONDBLCLK);
        MSGDEF(WM_SETFOCUS);
        MSGDEF(WM_KILLFOCUS);
        MSGDEF(WM_MOVE);
        MSGDEF(WM_SIZE);
        MSGDEF(WM_SIZING);
        MSGDEF(WM_MOVING);
        MSGDEF(WM_GETMINMAXINFO);
        MSGDEF(WM_CAPTURECHANGED);
        MSGDEF(WM_WINDOWPOSCHANGED);
        MSGDEF(WM_WINDOWPOSCHANGING);
        MSGDEF(WM_NCCALCSIZE);
        MSGDEF(WM_NCCREATE);
        MSGDEF(WM_NCDESTROY);
        MSGDEF(WM_TIMER);
        MSGDEF(WM_KEYDOWN);
        MSGDEF(WM_KEYUP);
        MSGDEF(WM_CHAR);
        MSGDEF(WM_SYSKEYDOWN);
        MSGDEF(WM_SYSKEYUP);
        MSGDEF(WM_SYSCOMMAND);
        MSGDEF(WM_SYSCHAR);
        MSGDEF(WM_VSCROLL);
        MSGDEF(WM_HSCROLL);
        MSGDEF(WM_CHAR);
        MSGDEF(WM_SHOWWINDOW);
        MSGDEF(WM_PARENTNOTIFY);
        MSGDEF(WM_CREATE);
        MSGDEF(WM_NCACTIVATE);
        MSGDEF(WM_ACTIVATE);
        MSGDEF(WM_ACTIVATEAPP);
        MSGDEF(WM_CLOSE);
        MSGDEF(WM_DESTROY);
        MSGDEF(WM_GETICON);
        MSGDEF(WM_GETTEXT);
        MSGDEF(WM_GETTEXTLENGTH);
        static TCHAR szMsg[10];
        ::wsprintf(szMsg, _T("0x%04X"), uMsg);
        return szMsg;
    }

    /////////////////////////////////////////////////////////////////////////////////////
    //
    //

    //////////////////////////////////////////////////////////////////////////
    //
    DUI_BASE_BEGIN_MESSAGE_MAP(CNotifyPump)
        DUI_END_MESSAGE_MAP()

        static const DUI_MSGMAP_ENTRY* DuiFindMessageEntry(const DUI_MSGMAP_ENTRY* lpEntry, TNotifyUI& msg)
    {
        CDuiString sMsgType = msg.sType;
        CDuiString sCtrlName = msg.pSender->GetName();
        const DUI_MSGMAP_ENTRY* pMsgTypeEntry = NULL;
        while (lpEntry->nSig != DuiSig_end)
        {
            if (lpEntry->sMsgType == sMsgType)
            {
                if (!lpEntry->sCtrlName.IsEmpty())
                {
                    if (lpEntry->sCtrlName == sCtrlName)
                    {
                        return lpEntry;
                    }
                }
                else
                {
                    pMsgTypeEntry = lpEntry;
                }
            }
            lpEntry++;
        }
        return pMsgTypeEntry;
    }

    bool CNotifyPump::AddVirtualWnd(CDuiString strName, CNotifyPump* pObject)
    {
        if (m_VirtualWndMap.Find(strName) == NULL)
        {
            m_VirtualWndMap.Insert(strName.GetData(), (LPVOID)pObject);
            return true;
        }
        return false;
    }

    bool CNotifyPump::RemoveVirtualWnd(CDuiString strName)
    {
        if (m_VirtualWndMap.Find(strName) != NULL)
        {
            m_VirtualWndMap.Remove(strName);
            return true;
        }
        return false;
    }

    bool CNotifyPump::LoopDispatch(TNotifyUI& msg)
    {
        const DUI_MSGMAP_ENTRY* lpEntry = NULL;
        const DUI_MSGMAP* pMessageMap = NULL;

#ifndef UILIB_STATIC
        for (pMessageMap = GetMessageMap(); pMessageMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)())
#else
        for (pMessageMap = GetMessageMap(); pMessageMap != NULL; pMessageMap = pMessageMap->pBaseMap)
#endif
        {
#ifndef UILIB_STATIC
            ASSERT(pMessageMap != (*pMessageMap->pfnGetBaseMap)());
#else
            ASSERT(pMessageMap != pMessageMap->pBaseMap);
#endif
            if ((lpEntry = DuiFindMessageEntry(pMessageMap->lpEntries, msg)) != NULL)
            {
                goto LDispatch;
            }
        }
        return false;

    LDispatch:
        union DuiMessageMapFunctions mmf;
        mmf.pfn = lpEntry->pfn;

        bool bRet = false;
        int nSig;
        nSig = lpEntry->nSig;
        switch (nSig)
        {
        default:
            ASSERT(FALSE);
            break;
        case DuiSig_lwl:
            (this->*mmf.pfn_Notify_lwl)(msg.wParam, msg.lParam);
            bRet = true;
            break;
        case DuiSig_vn:
            (this->*mmf.pfn_Notify_vn)(msg);
            bRet = true;
            break;
        }
        return bRet;
    }

    void CNotifyPump::NotifyPump(TNotifyUI& msg)
    {
        ///遍历虚拟窗口
        if (!msg.sVirtualWnd.IsEmpty()) {
            for (int i = 0; i < m_VirtualWndMap.GetSize(); i++) {
                if (LPCTSTR key = m_VirtualWndMap.GetAt(i)) {
                    if (_tcsicmp(key, msg.sVirtualWnd.GetData()) == 0) {
                        CNotifyPump* pObject = static_cast<CNotifyPump*>(m_VirtualWndMap.Find(key, false));
                        if (pObject && pObject->LoopDispatch(msg))
                            return;
                    }
                }
            }
        }

        ///
        //遍历主窗口
        LoopDispatch(msg);
    }

    //////////////////////////////////////////////////////////////////////////
    ///
    CWindowWnd::CWindowWnd()
        : m_hWnd(NULL)
        , m_OldWndProc(::DefWindowProc)
        , m_bSubclassed(false)
    {
    }

    HWND CWindowWnd::GetHWND() const
    {
        return m_hWnd;
    }

    CWindowWnd::operator HWND() const
    {
        return m_hWnd;
    }

    UINT CWindowWnd::GetClassStyle() const
    {
        return 0;
    }

    LPCTSTR CWindowWnd::GetSuperClassName() const
    {
        return NULL;
    }

    HWND CWindowWnd::CreateDuiWindow(HWND hwndParent, LPCTSTR pstrWindowName, DWORD dwStyle /*=0*/, DWORD dwExStyle /*=0*/)
    {
        return Create(hwndParent, pstrWindowName, dwStyle, dwExStyle, 0, 0, 0, 0, NULL);
    }

    HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, const RECT rc, HMENU hMenu)
    {
        return Create(hwndParent, pstrName, dwStyle, dwExStyle, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, hMenu);
    }

    HWND CWindowWnd::Create(HWND hwndParent, LPCTSTR pstrName, DWORD dwStyle, DWORD dwExStyle, int x, int y, int cx, int cy, HMENU hMenu)
    {
        if (GetSuperClassName() != NULL && !RegisterSuperClass())
            return NULL;

        if (GetSuperClassName() == NULL && !RegisterWindowClass())
            return NULL;

        // 创建窗体（CreateWindowEx or CreateWindow）
        m_hWnd = ::CreateWindowEx(dwExStyle, GetWindowClassName(), pstrName, dwStyle, x, y, cx, cy, hwndParent, hMenu, CPaintManagerUI::GetInstance(), this);
        ASSERT(m_hWnd != NULL);
        return m_hWnd;
    }

    /* #define SubclassWindow(hwnd, lpfn)       \
            ((WNDPROC)SetWindowLongPtr((hwnd), GWLP_WNDPROC, (LPARAM)(WNDPROC)(lpfn)))*/
    HWND CWindowWnd::Subclass(HWND hWnd)
    {
        ASSERT(::IsWindow(hWnd));
        ASSERT(m_hWnd == NULL);

        m_OldWndProc = SubclassWindow(hWnd, __WndProc);
        if (m_OldWndProc == NULL)
            return NULL;

        m_bSubclassed = true;
        m_hWnd = hWnd;
        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(this));
        return m_hWnd;
    }

    void CWindowWnd::UnSubclass()
    {
        ASSERT(::IsWindow(m_hWnd));
        if (!::IsWindow(m_hWnd))
            return;

        if (!m_bSubclassed)
            return;

        SubclassWindow(m_hWnd, m_OldWndProc);
        m_OldWndProc = ::DefWindowProc;
        m_bSubclassed = false;
    }

    void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
    {
        ASSERT(::IsWindow(m_hWnd));
        if (!::IsWindow(m_hWnd))
            return;

        // 设置窗体的现实状态
        ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
    }

    /*#define     GetWindowOwner(hwnd)    GetWindow(hwnd, GW_OWNER)*/
    UINT CWindowWnd::ShowModal()
    {
        ASSERT(::IsWindow(m_hWnd));

        UINT nRet = 0;
        HWND hWndParent = GetWindowOwner(m_hWnd);
        ::ShowWindow(m_hWnd, SW_SHOWNORMAL);

        // 禁止父窗体输入
        ::EnableWindow(hWndParent, FALSE);

        // 处理窗体消息
        MSG msg = { 0 };
        while (::IsWindow(m_hWnd) && ::GetMessage(&msg, NULL, 0, 0))
        {
            if (msg.message == WM_CLOSE && msg.hwnd == m_hWnd)
            {
                nRet = msg.wParam;
                ::EnableWindow(hWndParent, TRUE);
                ::SetFocus(hWndParent);
            }
            if (!CPaintManagerUI::TranslateMessage(&msg))
            {
                ::TranslateMessage(&msg);
                ::DispatchMessage(&msg);
            }
            if (msg.message == WM_QUIT)
                break;
        }

        // 恢复父窗体输入
        ::EnableWindow(hWndParent, TRUE);
        // 恢复焦点
        ::SetFocus(hWndParent);

        if (msg.message == WM_QUIT)
        {
            ::PostQuitMessage(msg.wParam);
        }
        return nRet;
    }

    void CWindowWnd::Close(UINT nRet)
    {
        ASSERT(::IsWindow(m_hWnd));
        if (!::IsWindow(m_hWnd))
            return;

        PostMessage(WM_CLOSE, (WPARAM)nRet, 0L);
    }

    void CWindowWnd::CenterWindow()
    {
        ASSERT(::IsWindow(m_hWnd));
        ASSERT((GetWindowStyle(m_hWnd) & WS_CHILD) == 0);

        RECT rcDlg = { 0 };
        ::GetWindowRect(m_hWnd, &rcDlg);
        RECT rcArea = { 0 };
        RECT rcCenter = { 0 };
        HWND hWnd = *this;
        // TODO: GetParent 与 GetWindowOwner的区别
        HWND hWndParent = ::GetParent(m_hWnd);
        HWND hWndCenter = ::GetWindowOwner(m_hWnd);
        if (hWndCenter != NULL)
            hWnd = hWndCenter;

        // 处理多显示器模式下屏幕居中
        MONITORINFO oMonitor = {};
        oMonitor.cbSize = sizeof(oMonitor);
        ::GetMonitorInfo(::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &oMonitor);
        rcArea = oMonitor.rcWork;
        if (hWndCenter == NULL || IsIconic(hWndCenter))
            rcCenter = rcArea;
        else
            ::GetWindowRect(hWndCenter, &rcCenter);

        int DlgWidth = rcDlg.right - rcDlg.left;
        int DlgHeight = rcDlg.bottom - rcDlg.top;

        // Find dialog's upper left based on rcCenter
        int xLeft = (rcCenter.left + rcCenter.right) / 2 - DlgWidth / 2;
        int yTop = (rcCenter.top + rcCenter.bottom) / 2 - DlgHeight / 2;

        // The dialog is outside the screen, move it inside
        if (xLeft < rcArea.left)
            xLeft = rcArea.left;
        else if (xLeft + DlgWidth > rcArea.right)
            xLeft = rcArea.right - DlgWidth;
        if (yTop < rcArea.top)
            yTop = rcArea.top;
        else if (yTop + DlgHeight > rcArea.bottom)
            yTop = rcArea.bottom - DlgHeight;
        ::SetWindowPos(m_hWnd, NULL, xLeft, yTop, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
    }

    void CWindowWnd::SetIcon(UINT nRes)
    {
        // UMU: 防止某些 DPI 设置下图标模糊
        //HICON hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
        HICON hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON, (::GetSystemMetrics(SM_CXICON) + 15) & ~15, (::GetSystemMetrics(SM_CYICON) + 15) & ~15, LR_DEFAULTCOLOR);
        ASSERT(hIcon);
        ::SendMessage(m_hWnd, WM_SETICON, (WPARAM)TRUE, (LPARAM)hIcon);

        // UMU: 防止某些 DPI 设置下图标模糊
        //hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
        hIcon = (HICON)::LoadImage(CPaintManagerUI::GetInstance(), MAKEINTRESOURCE(nRes), IMAGE_ICON, (::GetSystemMetrics(SM_CXSMICON) + 15) & ~15, (::GetSystemMetrics(SM_CYSMICON) + 15) & ~15, LR_DEFAULTCOLOR);
        ASSERT(hIcon);
        ::SendMessage(m_hWnd, WM_SETICON, (WPARAM)FALSE, (LPARAM)hIcon);
    }

    bool CWindowWnd::RegisterWindowClass()
    {
        // 设置窗口类信息
        WNDCLASS wc = { 0 };
        wc.style = GetClassStyle(); // 窗口类的风格
        wc.cbClsExtra = 0; // 窗口扩展
        wc.cbWndExtra = 0; // 窗口实例扩展
        wc.hIcon = NULL; // 窗口的最小化图标
        wc.lpfnWndProc = CWindowWnd::__WndProc; // 窗口处理函数
        wc.hInstance = CPaintManagerUI::GetInstance();  // 实例句柄
        wc.hCursor = ::LoadCursor(NULL, IDC_ARROW); // 窗口鼠标光标
        wc.hbrBackground = NULL; // 窗口背景色
        wc.lpszMenuName = NULL; // 窗口菜单
        wc.lpszClassName = GetWindowClassName(); // 窗口类名

        // 注册窗体
        ATOM ret = ::RegisterClass(&wc);
        ASSERT(ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS);
        return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
    }

    bool CWindowWnd::RegisterSuperClass()
    {
        // Get the class information from an existing
        // window so we can subclass it later on...
        WNDCLASSEX wc = { 0 };
        wc.cbSize = sizeof(WNDCLASSEX);
        // 获取窗体类信息
        if (!::GetClassInfoEx(NULL, GetSuperClassName(), &wc))
        {
            if (!::GetClassInfoEx(CPaintManagerUI::GetInstance(), GetSuperClassName(), &wc))
            {
                ASSERT(!"Unable to locate window class");
                return NULL;
            }
        }
        m_OldWndProc = wc.lpfnWndProc;
        wc.lpfnWndProc = CWindowWnd::__ControlProc; // 替换窗体处理函数
        wc.hInstance = CPaintManagerUI::GetInstance();
        wc.lpszClassName = GetWindowClassName();

        // 注册窗体
        ATOM ret = ::RegisterClassEx(&wc);
        ASSERT(ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS);
        return ret != NULL || ::GetLastError() == ERROR_CLASS_ALREADY_EXISTS;
    }

    // SetWindowLongPtr: 改变指定窗口的属性
    LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CWindowWnd* pThis = NULL;
        if (uMsg == WM_NCCREATE)
        {
            LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
            pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
            pThis->m_hWnd = hWnd;
            ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
        }
        else
        {
            pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
            if (uMsg == WM_NCDESTROY && pThis != NULL)
            {
                LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
                ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
                // 如果是子类化，先取消子类化
                if (pThis->m_bSubclassed)
                    pThis->UnSubclass();
                pThis->m_hWnd = NULL;
                pThis->OnFinalMessage(hWnd);
                return lRes;
            }
        }
        if (pThis != NULL)
        {
            return pThis->HandleMessage(uMsg, wParam, lParam);
        }
        else
        {
            return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }

    LRESULT CALLBACK CWindowWnd::__ControlProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CWindowWnd* pThis = NULL;
        if (uMsg == WM_NCCREATE)
        {
            LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
            pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
            ::SetProp(hWnd, _T("WndX"), (HANDLE)pThis);
            pThis->m_hWnd = hWnd;
        }
        else
        {
            pThis = reinterpret_cast<CWindowWnd*>(::GetProp(hWnd, _T("WndX")));
            if (uMsg == WM_NCDESTROY && pThis != NULL)
            {
                LRESULT lRes = ::CallWindowProc(pThis->m_OldWndProc, hWnd, uMsg, wParam, lParam);
                if (pThis->m_bSubclassed)
                    pThis->UnSubclass();
                ::SetProp(hWnd, _T("WndX"), NULL);
                pThis->m_hWnd = NULL;
                pThis->OnFinalMessage(hWnd);
                return lRes;
            }
        }

        if (pThis != NULL)
        {
            return pThis->HandleMessage(uMsg, wParam, lParam);
        }
        else
        {
            return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }

    LRESULT CWindowWnd::SendMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
    {
        ASSERT(::IsWindow(m_hWnd));
        return ::SendMessage(m_hWnd, uMsg, wParam, lParam);
    }

    LRESULT CWindowWnd::PostMessage(UINT uMsg, WPARAM wParam /*= 0*/, LPARAM lParam /*= 0*/)
    {
        ASSERT(::IsWindow(m_hWnd));
        return ::PostMessage(m_hWnd, uMsg, wParam, lParam);
    }

    void CWindowWnd::ResizeClient(int cx /*= -1*/, int cy /*= -1*/)
    {
        ASSERT(::IsWindow(m_hWnd));

        RECT rc = { 0 };
        if (!::GetClientRect(m_hWnd, &rc))
            return;

        if (cx != -1) rc.right = cx;
        if (cy != -1) rc.bottom = cy;
        // 依据所需客户矩形的大小，计算需要的窗口矩形的大小
        if (!::AdjustWindowRectEx(&rc, GetWindowStyle(m_hWnd), (!(GetWindowStyle(m_hWnd) & WS_CHILD) && (::GetMenu(m_hWnd) != NULL)), GetWindowExStyle(m_hWnd)))
            return;

        ::SetWindowPos(m_hWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
    }

    LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
    }

    void CWindowWnd::OnFinalMessage(HWND /*hWnd*/)
    {
    }

} // namespace DuiLib
